package procilege.lang;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.solr.util.OpenBitSet;

import procilege.impl.NameImpl;

public class PrivExprMergence implements PrivExprElement {
	
	private OpenBitSet boundnames = new OpenBitSet();
	
	private Set<PrivExprElement> privileges = new HashSet<PrivExprElement>();

	private BoolExprElement restrictionExpr = null;

	private EntityExprElement entityExpr = null;

	public PrivExprMergence(OpenBitSet names) {

		if (names != null)
			boundnames.or(names);
	}

	public BoolExprElement getRestrictionExpr() {
		return restrictionExpr;
	}

	public Collection<PrivExprElement> getPrivileges() {
		return privileges;
	}

	public EntityExprElement getEntityExpr() {
		return entityExpr;
	}

	public void mergeEntity(EntityExprElement expr) {

		EntityExprAnd r;
		if (this.entityExpr == null)
			r = new EntityExprAnd();
		else
			r = (EntityExprAnd) this.entityExpr;

		r.and(expr);

		entityExpr = r;
	}

	public void mergeRestriction(BoolExprElement expr) {

		BoolExprAnd r = new BoolExprAnd();
		r.and(restrictionExpr);
		r.and(expr);

		restrictionExpr = r;
	}

	public void mergePrivilege(PrivExprElement term) {

		if (term == null)
			return;

		if (term instanceof PrivExprMergence) {

			PrivExprMergence prod = (PrivExprMergence) term;

			mergeEntity(prod.getEntityExpr());
			mergeRestriction(prod.getRestrictionExpr());

			for (PrivExprElement t : prod.privileges) {

				mergePrivilege(t);
			}

			boundnames.or(prod.boundnames);
		} else {

			privileges.add(term);
		}
	}
	
	public OpenBitSet getFreeNames() {
		
		OpenBitSet names = new OpenBitSet();
		pname(this, names);
		
		names.andNot(boundnames);
		
		return names;
	}
	
	private void pname(PrivExprElement pexpr, OpenBitSet names) {
		
		if (pexpr instanceof PrivExprMergence) {
			
			PrivExprMergence m = (PrivExprMergence)pexpr;
			for (PrivExprElement pe: m.getPrivileges()) {
				pname(pe, names);
			}
		} else if (pexpr instanceof PrivExprComposition) {
			
			PrivExprComposition m = (PrivExprComposition)pexpr;

			for (PrivExprElement pe: m.getTerms()) {
				pname(pe, names);
			}
		} else if (pexpr instanceof NameImpl) {
			
			names.set(((NameImpl)pexpr).nameId);
		} else if (pexpr instanceof ExprSubstitute) {
			
			ExprSubstitute subst = (ExprSubstitute) pexpr;
			pname((PrivExprElement)subst.getExpr(), names);
		} else
			throw new UnsupportedOperationException("unkown type of name: " + pexpr);
	}

	public OpenBitSet getBoundNames() {
		return boundnames;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((entityExpr == null) ? 0 : entityExpr.hashCode());
		result = prime * result
				+ ((privileges == null) ? 0 : privileges.hashCode());
		result = prime * result
				+ ((restrictionExpr == null) ? 0 : restrictionExpr.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final PrivExprMergence other = (PrivExprMergence) obj;
		if (entityExpr == null) {
			if (other.entityExpr != null)
				return false;
		} else if (!entityExpr.equals(other.entityExpr))
			return false;
		if (privileges == null) {
			if (other.privileges != null)
				return false;
		} else if (!privileges.equals(other.privileges))
			return false;
		if (restrictionExpr == null) {
			if (other.restrictionExpr != null)
				return false;
		} else if (!restrictionExpr.equals(other.restrictionExpr))
			return false;
		return true;
	}
}